package mark.miniboard;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.PopupWindow;
import android.widget.TextView;

/** Handles display of and input to the keyboard */
public class MiniboardView extends View {

    /** the distance above the pointer in pixels at which to draw the key preview */
    private static final int PREVIEW_YOFF = 160;

    /** the total height of the keyboard in pixels*/
    private static final int HEIGHT = 70;

    /** the vertical distance that the pointer must move, in pixels, in order to 
     * generate an upper row key press */
    private static final int UPWARD_THRESHOLD = 20;

    /** the vertical distance that the pointer must move, in pixels, in order to 
     * generate a lower row key press */
    private static final int DOWNWARD_THRESHOLD = -2;
    
    /** the keyboard service */
    private Miniboard miniboard;
    
    /** the y-coordinate of the initial key press */
    private int y;
    
    /** the paint object used to render key backgrounds */
    private Paint keyPaint;
    
    /** the paint object used to render key text */
    private TextPaint textPaint;

    /** the current keys */
    private String[][] keys;

    /** the array of keyboard key columns */
    private KeyColumn[] columns;
    
    /** the key preview pop-up window */
    private PopupWindow popup;
    
    /** the text displayed on the key preview pop-up window */
    private TextView preview;

    /**
     * Constructs a new MiniboardView
     * @see View#View(Context, AttributeSet)
     */
    public MiniboardView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Constructs a new MiniboardView
     * @see View#View(Context, AttributeSet, int)
     */
    public MiniboardView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        
        miniboard = (Miniboard) context;
        
        keyPaint = new Paint();
        
        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.bgColor = Color.BLACK;
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(20);
        
        preview = new TextView(this.getContext());
        preview.setTextColor(Color.WHITE);
        preview.setBackgroundColor(Color.BLACK);
        preview.setTextSize(36);
        preview.setShadowLayer(1, 1, 1, Color.BLACK);
        
        popup = new PopupWindow(this.getContext());
        popup.setContentView(preview);
    }

    /**
     * Sets the current keys
     * @param keys the keys to use
     */
    public void setKeys(String[][] keys) {
        this.keys = keys;
        columns = new KeyColumn[keys.length];
        for (int i = 0; i < keys.length; ++i) {
            columns[i] = new KeyColumn(keys[i]);
        }
        this.onLayout(false, this.getLeft(), this.getTop(), 
            this.getRight(), this.getBottom());
        this.postInvalidate();
    }
    
    /**
     * @return the current keys
     */
    public String[][] getKeys() {
        return keys;
    }

    @Override
    protected void onMeasure(int measuredWidth, int measuredHeight) {
        setMeasuredDimension(MeasureSpec.getSize(measuredWidth), HEIGHT);
    }

    /**
     * Resizes the width of the key columns
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        for (int i = 0; i < columns.length; ++i) {
            columns[i].setBounds(new Rect(
                    i*(getWidth()/columns.length), top, 
                    (i+1)*(getWidth()/columns.length), bottom));
        }
    }

    /**
     * Draws they keyboard
     */
    @Override
    protected void onDraw(Canvas canvas) {
        
        FontMetrics fm = textPaint.getFontMetrics();
        int w = canvas.getWidth()/columns.length;
        Rect bounds = new Rect();
        for (int i = 0; i < columns.length; ++i) {
            
            keyPaint.setColor(Color.GRAY);
            canvas.drawRect(i*w, 0, (i+1)*w, getHeight(), keyPaint);
            keyPaint.setColor(Color.BLACK);
            canvas.drawRect(i*w+1, 0, (i+1)*w-1, getHeight(), keyPaint);
            
            for (int j = 0; j < columns[i].getKeys().length; ++j) {
                String key = columns[i].getKeys()[j];
                textPaint.getTextBounds(key, 0, key.length(), bounds);
                canvas.drawText(key, i*w+w/2F-bounds.width()/2F, 
                    (j+1)/4F*HEIGHT - (j+1)/4F*fm.ascent, textPaint);
            }
        }
    }

    /**
     * Handles key presses
     */
    @Override
    public boolean onTouchEvent(MotionEvent me) {
        
        int x = Math.round(Math.min(Math.max(me.getX(), 0), getWidth()-1));
        if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
            y = Math.round(me.getY());
        }
        
        for (KeyColumn col : columns) {
            if (col.getBounds().contains(x, y)) {
                String key = col.getKeys()[getCharacterIndex(me)];
                if (me.getActionMasked() == MotionEvent.ACTION_UP) {
                    miniboard.onRelease(key);
                    popup.dismiss();
                } else {
                    preview.setText(key);
                    Rect bounds = new Rect();
                    preview.getPaint().getTextBounds(key, 0, key.length(), bounds);
                    if (popup.isShowing()) {
                        popup.update(x - bounds.width()/2, (int)me.getY()-PREVIEW_YOFF, 
                            bounds.width()+16, 80);
                    } else {
                        popup.setWidth(bounds.width()+16);
                        popup.setHeight(80);
                        popup.showAtLocation(this, Gravity.NO_GRAVITY,
                            x - bounds.width(), (int)me.getY()-PREVIEW_YOFF);
                    }
                    miniboard.onPress(key);
                }
                break;
            }
        }

        return true;
    }

    /**
     * Returns a key column array character index based on the vertical offset
     * of the given MotionEvent from the initial pointer location
     * @param me the current pointer location
     * @return the corresponding key column array character index
     */
    private int getCharacterIndex(MotionEvent me) {
        
        int motion = y - Math.round(me.getY());
        if (motion > UPWARD_THRESHOLD) {
            return 0;
        } else if (motion < DOWNWARD_THRESHOLD) {
            return 2;
        } else {
            return 1;
        }
    }
}

